package agngui.ui;
import agngui.agnmodel.*;
import agngui.event.AGNEvent;
import agngui.event.AGNListener;
import agngui.event.AGNPublisher;
import agngui.event.FieldChangedEvent;
import agngui.event.HandleDragEvent;
import agngui.event.HandleReleaseEvent;
import agngui.event.HandleSelectEvent;
import agngui.event.ObjectHideEvent;
import agngui.event.ObjectReleaseEvent;
import agngui.event.ObjectSelectEvent;
import agngui.event.ObjectShowEvent;
import agngui.tools.*;

import javax.swing.*;

import javax.media.j3d.*;
import javax.vecmath.*;

import java.awt.event.*;
import java.awt.*;
import java.awt.image.*;

import java.util.*;

/**
 * The main UI of the program.
 * 
 * This creates the image viewing window, and also spawns up the Configuration window.
 * Allows for changing geometries, as well as taking a screenshot of the current viewable image.
 * 
 * 
 * @author Ben Bloom
 * @author Phil Johnson
 */
public class ScatteringGUI implements ObjectDisplayer, AGNListener {

	/**
	 * the operating system's end of line character
	 */
	static String endl = System.getProperty("line.separator");
	
	/**
	 * the separator used to denote separations in directories and files
	 */
	static String file_separator = System.getProperty("file.separator");
	
	/**
	 * the number of degrees to shift per pixel change on the screen, used in Free-Look
	 */
	private static final double DEGREES_PER_PIXEL = 0.25;
	
	/**
	 * a helper tool that allows for capturing a screenshot
	 */
	private static Robot robot;
	static {
		try {
			robot = new Robot();
		} catch(java.awt.AWTException e) {
			System.err.println("Robot failed to initialize");
		}
	};
	
	/**
	 * the window that contains the main UI
	 */
    private JFrame        window;
    
    /**
     * a handle to the configuraton window
     */
    private ConfigWindow  config_window;
	 
    /**
     * the renderer for the Java3D canvas
     */
    private AGNModelRenderer   modelRenderer;
    
    /**
     * the geometry
     */
    private AGNModelGeometry   modelGeometry;
    
    /**
     * the active handle in the system
     */
    private AGNParameterHandle activeHandle;
    
    /**
     * the Camera being used to view the scene
     */
	private AGNCamera camera;
	
	/**
	 * the disk
	 */
	private AGNObject disk;
	
	/**
	 * the torus
	 */
	private AGNObject torus;
	
	/**
	 * the cone
	 */
	private AGNObject cone;
	
	/**
	 * the equatorial region
	 */
	private AGNObject equatorial;
	
	/**
	 * the wind region
	 */
	private AGNObject wind;
	
	/**
	 * a mapping from each object's name to the object itself
	 */
	private Map<String, AGNObject> scene_objects;
	
	/**
	 * the object that is currently being operated upon in the UI
	 */
	private AGNObject pickedObject;

	/**
	 * the button that signifies zooming is selected
	 */
	private JRadioButton zoom_button;
		
	/**
	 * the button that signifies that the user is panning the camera around
	 */
	private JRadioButton pan_button;
	
	/**
	 * snaps the user's view to to system inclination
	 */
	private JButton snap_button;
	
	/**
	 * the progress made in generating files
	 */
	private JProgressBar progress_bar;
	
	/**
	 * the current status of operation, used when automation is occurring
	 */
	private JTextField status_field;
	
	
	/**
	 * the stack of fields that have been edited, used for Undo if desired
	 */
	static LinkedList<Field> recently_edited_fields = new LinkedList<Field>();
	
	/**
	 * the currently selected background color
	 */
	private Color current_background_color;
	
	/**
	 * defines a state for the camera to use, makes telling what the camera is doing
	 * at certain points much easier to figure out.
	 * 
	 * @author Ben Bloom
	 */
	static enum CAMERA_STATE {
		FREE_LOOK,
		ZOOM,
		PAN,
		NONE,
	};
	
	/**
	 * the current state of the camera
	 */
	private CAMERA_STATE camera_state;
	
	
	/**
	 * the camera's x coordinate field
	 */
	private JTextField camera_x;
	
	/**
	 * the camera's y coordinate field
	 */
	private JTextField camera_y;
	
	/**
	 * the camera's z coordinate field
	 */
	private JTextField camera_z;
	
	/**
	 * the old x position of the mouse
	 */
	private int old_mouse_x;
	
	/**
	 * the old y position of the mouse
	 */
	private int old_mouse_y;
	
	/**
	 * constructs a new GUI for viewing the system.
	 */
    public ScatteringGUI() {
        window = new JFrame("Visualization");
        window.setSize(600, 600);
        
        window.setLocationByPlatform(true);
        
		camera_x = new JTextField(6);
		camera_y = new JTextField(6);
		camera_z = new JTextField(6);
		camera_state = CAMERA_STATE.NONE;
		old_mouse_x = 0;
		old_mouse_y = 0;
		current_background_color = Color.BLACK;
		
		// Set up the model renderer
		modelRenderer = new AGNModelRenderer( window );
		camera = modelRenderer.getCamera();
		camera.setFields(camera_x, camera_y, camera_z);
		camera.snap();
		
		progress_bar = new JProgressBar(0, 100);
		progress_bar.setStringPainted(true);
		status_field = new JTextField(15);
		status_field.setEditable(false);
		status_field.setText("Ready");
		
        final AGNModelCanvas canvas = modelRenderer.getCanvas();
		
    	canvas.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
                AGNModelCanvas canvas = (AGNModelCanvas)modelRenderer.getCanvas();
				int x = e.getX();
				int y = e.getY();
            
                if( pickedObject == null ) {
                    pickedObject = modelGeometry.pickObject( modelRenderer, x, y );    
                } else {
                    // Check the handle first, THEN check the object
                    if( (activeHandle = pickedObject.getIntersectingHandle( x, y, canvas)) != null ) {
                        //modelRenderer.grabSelectPoint( activeHandle, activeHandle.getPoint() );
                        AGNPublisher.dispatchEvent(new HandleSelectEvent(activeHandle));
                    } else {
                        pickedObject = modelGeometry.pickObject( modelRenderer, x, y );
                    }
                }

                if( pickedObject != null ) {
                    //id = pickedObject.getIdentifier();
					
					AGNPublisher.dispatchEvent(new ObjectSelectEvent(pickedObject));
					
    				modelRenderer.getCanvas().requestFocus();
                } else {
					AGNPublisher.dispatchEvent(new ObjectReleaseEvent());
                }
			}

            public void mouseReleased( MouseEvent e ) {
                if(activeHandle != null) {
                	//System.out.println("ScatteringGUI.mouseReleased(): generating handle released event");
	            	AGNPublisher.dispatchEvent(new HandleReleaseEvent(activeHandle));
					activeHandle = null;
                }
            }
		});

		
        
        canvas.addMouseMotionListener( new MouseMotionAdapter() {
            public void mouseDragged( MouseEvent e ) {
                int x = e.getX();
                int y = e.getY();

                // Not interested in continuing if there's no active handle
                if( activeHandle != null ) {
                    // Update the selected point's world coordinates
                    Point3d currentPoint = activeHandle.projectClickOntoControlAxis( x, y, modelRenderer );
                    AGNPublisher.dispatchEvent(new HandleDragEvent(activeHandle, currentPoint));
                }
            }
        });
		
		canvas.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				//this listener will only deal with the picked object, so if it's null, there's nothing to be done.
				if(pickedObject == null) {
					return;
				}
				
				int code = e.getKeyCode();
				if(code == KeyEvent.VK_ESCAPE) {
					String id = pickedObject.getIdentifier();
					pickedObject = null;
					//keep the object showing, but release the handles
					displayObject(id, true);
					AGNPublisher.dispatchEvent(new ObjectReleaseEvent());
				} else if(code == KeyEvent.VK_DELETE) {
					//hiding the object also releases the handles.
					displayObject(pickedObject.getIdentifier(), false);
				}
			}
		});
		
		
		// Add geometry
        modelGeometry = new AGNModelGeometry();
		disk = new AGNDisk(1.542840125E15, 3.08568025E15, 40, modelGeometry);
		equatorial = new AGNEquatorialRegion(3.08568025E15, 3.08568025E16, 85, 40, modelGeometry);
		torus = new AGNTorus(3.08568025E16, 6.1713605E16, 25, 20, 40, modelGeometry);
		cone = new AGNCone(3.08568025E16, 3.08568025E18, 45, 40, false, modelGeometry);
        wind = new AGNWind(3.08568025E16, 3.08568025E17, 30, 45, 40, modelGeometry);
		
        disk.setShowing(true);
        scene_objects = new HashMap<String, AGNObject>();
		scene_objects.put(ConfigWindow.panel_names[0], disk);
		scene_objects.put(ConfigWindow.panel_names[1], torus);
		scene_objects.put(ConfigWindow.panel_names[2], equatorial);
		scene_objects.put(ConfigWindow.panel_names[3], wind);
		scene_objects.put(ConfigWindow.panel_names[4], cone);
		
        AGNPublisher.addAGNListener(this);
        config_window = new ConfigWindow(status_field, progress_bar, this);
        AGNPublisher.addAGNListener(canvas);
        AGNPublisher.addAGNListener(config_window);

        
		if(config_window.doCone()) {
			((AGNCone)cone).showCountercone(config_window.doCountercone());
		}

		pickedObject = null;
		
		modelGeometry.addObject( disk );
		modelGeometry.addObject( torus );
		modelGeometry.addObject( cone );
		modelGeometry.addObject( wind );
		modelGeometry.addObject( equatorial );
		
		displayObject("Torus", config_window.doTorus());
		displayObject("Equatorial", config_window.doEquatorial());
		displayObject("Cone", config_window.doCone());
		displayObject("Wind", config_window.doWind());
		
		// Build the scene
		modelRenderer.buildScene( modelGeometry );
		
        initializeMenuBar();

		window.getContentPane().add(generateCameraPanel(), BorderLayout.NORTH);
		
		JPanel south_pane = new JPanel(new GridLayout(0, 1));
		JPanel status_pane = new JPanel(new GridLayout(1, 0));
		
		//south_pane.add(config_window.which_region_panel);
		south_pane.add(status_pane);
		status_pane.add(status_field);
		status_pane.add(progress_bar);
		
		window.getContentPane().add(south_pane, BorderLayout.SOUTH);
		
        window.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                window.dispose();
                System.exit(0);
            }
        });
		
        window.addComponentListener(new ComponentAdapter() {
        	public void componentResized(ComponentEvent e) {
        		canvas.repaint();
        	}
        });
        
        window.setVisible(true);
		window.requestFocus();
    }
	
	/**
	 * implements ObjectDisplayer.displayObject(String, boolean)
	 * 
	 * Displays the object with the given id, if show is true.  Stops displaying it if show is false.
	 * If the id is invalid (the object does not exist), then nothing happens.
	 */
	public void displayObject(String id, boolean show) {
		AGNObject object = scene_objects.get(id);
		if(object != null) {
			config_window.setRegion(id, show);
			if(show) {
				AGNPublisher.dispatchEvent(new ObjectShowEvent(object));
			} else {
				AGNPublisher.dispatchEvent(new ObjectHideEvent(object));
				if(object == pickedObject) {
					AGNPublisher.dispatchEvent(new ObjectReleaseEvent());
				}
			}
		}
	}
	
	/**
	 * implements AGNListener.processEvent(AGNEvent)
	 * 
	 * processes AGNEvents that are sent into the system which are pertinent to
	 * the UI needing to know how to change objects.
	 */
	public void processEvent(AGNEvent event) {
		if(event instanceof FieldChangedEvent) {
			//System.out.println("ScatteringGUI.processEvent(): caught FieldChangedEvent");
			FieldChangedEvent fce = (FieldChangedEvent)event;
			Field field = fce.getField();
			if(!fce.isUndo()) {
				recently_edited_fields.addFirst(field);
			}
			Double new_value = fce.getNewValue();
			//System.out.println("setting to " + new_value);
			String field_name = field.getName();
			if(field_name.equals(ConfigWindow.system_strings[0])) {
				camera.snapInclination(new_value);
			} else {
				AGNObject object = scene_objects.get(field.getParentPanel().getName());
				//System.out.println(object);
				if(object != null) {
					pickedObject = object;
					activeHandle = object.getHandle(field.getName().trim());
					//System.out.println(activeHandle);
					if(activeHandle != null) {
						//rotational parameters should be given in radians, it's simply
						//easier to do it here, since everywhere else, they're coming in
						//as radians.
						if(activeHandle instanceof AGNRotationalParameterHandle ||
                           activeHandle instanceof AGNAngularParameterHandle ) {
							new_value = Math.PI * new_value / 180;
						}
						//System.out.println("ScatteringGUI calling object.resize(): " + object.getIdentifier() + " " + activeHandle.getIdentifier() + " " + new_value);
						object.resize(activeHandle, new_value);
					}
				}
			}
		}
	}
	
	/**
	 * Creates the camera control panel as a detachable tool bar
	 * 
	 * @return a JToolBar containing all of the camera's controls
	 */
	private JToolBar generateCameraPanel() {
	
		JToolBar camera_panel = new JToolBar();
		camera_panel.setLayout(new GridLayout(0,1));
		camera_panel.setFloatable(true);
		
		ButtonGroup group = new ButtonGroup();
		zoom_button = new JRadioButton("Zoom");
		pan_button = new JRadioButton("Pan");
		
		group.add(zoom_button);
		group.add(pan_button);
		
		
		JPanel temp = new JPanel(new SpringLayout());
		temp.add(zoom_button);
		temp.add(pan_button);
		
		camera_panel.add(temp);
		
		final JTextField degree_factor_x_label = new JTextField("degrees");
		degree_factor_x_label.setEditable(false);
		final JTextField degree_factor_x_field = new JTextField(4);
		degree_factor_x_field.setText("1");//just set a default value for it...
		
		JPanel smash = new JPanel(new BorderLayout());
		smash.add(degree_factor_x_label, BorderLayout.WEST);
		smash.add(degree_factor_x_field, BorderLayout.CENTER);
		
		
		snap_button = new JButton("Snap to System Inclination");
		temp.add(smash);
		smash = new JPanel();
		smash.add(snap_button);
		temp.add(smash);
		SpringUtilities.makeCompactGrid(temp, 1, 4, 0, 0, 10, 10);
		smash = null;
		
		temp = new JPanel(new GridLayout(2, 3));
		temp.add(new JLabel("Camera X"));
		temp.add(new JLabel("Camera Y"));
		temp.add(new JLabel("Camera Z"));
		temp.add(camera_x);
		temp.add(camera_y);
		temp.add(camera_z);
		camera_panel.add(temp);
		
		camera_x.setEditable(false);
		camera_y.setEditable(false);
		camera_z.setEditable(false);
		
		snap_button.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				RegionConfigPanel panel = config_window.getPanel(ConfigWindow.panel_names[9]);
				Field field = panel.getFieldMap().get(ConfigWindow.system_strings[0].trim());
				field.pressEnter();
			}
		});
		
		ActionListener radio_action = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(pan_button.isSelected()) {
					degree_factor_x_label.setText("degrees");
					if(degree_factor_x_field.getText().equals("")) {
						degree_factor_x_field.setText("1");
					}
					if(pan_button.isSelected()) {
						camera_state = CAMERA_STATE.PAN;
					} else {
						camera_state = CAMERA_STATE.NONE;
					}
				} else if(zoom_button.isSelected()) {
					degree_factor_x_label.setText("factor");
					if(degree_factor_x_field.getText().equals("") || degree_factor_x_field.getText().equals("1")) {
						degree_factor_x_field.setText("1.1");
					}
					camera_state = CAMERA_STATE.ZOOM;
				}
				modelRenderer.getCanvas().requestFocus();
			}
		};
		zoom_button.addActionListener(radio_action);
		pan_button.addActionListener(radio_action);
		
		zoom_button.setMnemonic(KeyEvent.VK_Z);
		pan_button.setMnemonic(KeyEvent.VK_P);
		
		pan_button.setSelected(true);
		
		KeyListener set_camera_listener = new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if(e.getKeyCode() != KeyEvent.VK_ENTER) {
					return;
				}
				
				try {
					double x = Double.parseDouble(degree_factor_x_field.getText());
					if(zoom_button.isSelected()) {
						camera.zoom(x);
						camera_state = CAMERA_STATE.ZOOM;
					}
				} catch(NumberFormatException nfe) {
					error(degree_factor_x_label.getText() + " must be a real number");
				}
			}
		};
		
		degree_factor_x_field.addKeyListener(set_camera_listener);
		final Canvas3D canvas = modelRenderer.getCanvas();
		
		canvas.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				int code = e.getKeyCode();
				if(e.isAltDown() || e.isControlDown()) {
					return;
				}
				
				if(code == KeyEvent.VK_Z) {
					if(camera_state == CAMERA_STATE.ZOOM) {
						camera_state = CAMERA_STATE.NONE;
					} else {
						camera_state = CAMERA_STATE.ZOOM;
						zoom_button.doClick();
					}
				} else if(code == KeyEvent.VK_P) {
					if(camera_state == CAMERA_STATE.PAN) {
						camera_state = CAMERA_STATE.NONE;
					} else {
						camera_state = CAMERA_STATE.PAN;
						pan_button.doClick();
					}
				}
				if(code != KeyEvent.VK_LEFT && code != KeyEvent.VK_RIGHT && code != KeyEvent.VK_UP && code != KeyEvent.VK_DOWN) {
					return;
				}
				boolean zoom = zoom_button.isSelected();
				boolean pan = pan_button.isSelected();
				double amount = 1;
				try {
					amount = Double.parseDouble(degree_factor_x_field.getText());
					
					if(code == KeyEvent.VK_UP) {
						if(zoom) { 
							camera.zoom(amount); 
						} else if(pan) {
							camera.pan(amount, false);
						}
					} else if(code == KeyEvent.VK_LEFT) {
						if(pan) {
							camera.pan(-amount, true);
						}
					} else if(code == KeyEvent.VK_RIGHT) {
						if(pan) {
							camera.pan(amount, true);
						}
					} else if(code == KeyEvent.VK_DOWN) {
						amount = -amount;
						if(zoom) { 
							camera.zoom(amount); 
						} else if(pan) {
							camera.pan(amount, false);
						}
					}
				} catch(NumberFormatException nfe) {
					error("Invalid input for camera control: " + degree_factor_x_field.getText());
				}
			}
		});
		
		canvas.addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseMoved(MouseEvent e) {
				if(camera_state == CAMERA_STATE.FREE_LOOK) {
					int new_x = e.getX();
					int new_y = e.getY();
					int dx = new_x - old_mouse_x;
					int dy = new_y - old_mouse_y;
					double theta_x = DEGREES_PER_PIXEL * dx;
					double theta_y = DEGREES_PER_PIXEL * dy;
					camera.yaw(theta_x);
					camera.pitch(-theta_y);
					
					old_mouse_x = new_x;
					old_mouse_y = new_y;
				}
			}
		});
		
		
		return camera_panel;
	}

	/**
	 * Creates the JMenuBar to be used for this window
	 */
    private void initializeMenuBar() {
        final JMenuBar menu_bar = new JMenuBar();
        
        window.setJMenuBar(menu_bar);
        final JMenu file_menu = new JMenu("File");
        JMenu edit_menu = new JMenu("Edit");
        JMenu view_menu = new JMenu("View");
        JMenu help_menu = new JMenu("Help");
		
		file_menu.setMnemonic(KeyEvent.VK_F);
		edit_menu.setMnemonic(KeyEvent.VK_E);
		view_menu.setMnemonic(KeyEvent.VK_V);
		help_menu.setMnemonic(KeyEvent.VK_H);
		

		JMenuItem screenshot_item = new JMenuItem("Screen Capture");
        JMenuItem exit_item = new JMenuItem("Exit");
       
		final Canvas3D canvas = modelRenderer.getCanvas();

		screenshot_item.setMnemonic(KeyEvent.VK_C);
		exit_item.setMnemonic(KeyEvent.VK_X);
		
		
		screenshot_item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new Thread() {
					public void run() {
						Rectangle rect = new Rectangle(canvas.getLocationOnScreen(), canvas.getSize());
						try {
							Thread.sleep(100);//just give the JMenu enough time to get out of the way
						} catch(InterruptedException ie) {
							
						}
						BufferedImage screenshot = robot.createScreenCapture(rect);
						new ImageFrame(screenshot);
					}
				}.start();
			}
		});
        
        exit_item.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                window.dispatchEvent(new WindowEvent(window, WindowEvent.WINDOW_CLOSING));
            }
        });
        
		file_menu.add(screenshot_item);
        file_menu.addSeparator();
        file_menu.add(exit_item);
        
        
        JMenuItem undo_item = new JMenuItem("Undo");
        JMenuItem background_item = new JMenuItem("Background Color");
        undo_item.setMnemonic(KeyEvent.VK_U);
        background_item.setMnemonic(KeyEvent.VK_C);
        
        undo_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		if(recently_edited_fields.size() > 0) {
	        		Field field = recently_edited_fields.removeFirst();
	        		field.pop_state();
        		}
        	}
        });
        
        background_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, "Choose Background Color", current_background_color);
        		if(color != null) {
	        		Color3f new_color = new Color3f(color.getRed(), color.getGreen(), color.getBlue());
	        		new_color.scale(1.0f/255.0f);
	        		modelRenderer.setBackgroundColor(new_color);
	        		current_background_color = color;
        		}
        	}
        });


        edit_menu.add(undo_item);
        edit_menu.addSeparator();
        edit_menu.add(background_item);
        edit_menu.add(createHudMenu());
        
        
        JMenuItem config_item = new JMenuItem("Configuration");
        config_item.setMnemonic(KeyEvent.VK_C);
        
        config_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		config_window.show();
        	}
        });
        JMenuItem converter_item = new JMenuItem("Unit Conversions");
        converter_item.setMnemonic(KeyEvent.VK_U);
        converter_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		ConversionWindow.getInstance();
        	}
        });
        
        view_menu.add(config_item);
        view_menu.add(converter_item);
        
        JMenu disk_menu = createObjectMenu(disk);
        JMenu torus_menu = createObjectMenu(torus);
        JMenu equatorial_menu = createObjectMenu(equatorial);
        JMenu wind_menu = createObjectMenu(wind);
        JMenu cone_menu = createObjectMenu(cone);
        
        disk_menu.setMnemonic(KeyEvent.VK_D);
        torus_menu.setMnemonic(KeyEvent.VK_T);
        equatorial_menu.setMnemonic(KeyEvent.VK_Q);
        wind_menu.setMnemonic(KeyEvent.VK_W);
        cone_menu.setMnemonic(KeyEvent.VK_C);
        
        
        
        JMenuItem how_to_item = new JMenuItem("How to Use");
        how_to_item.setMnemonic(KeyEvent.VK_H);
        help_menu.add(how_to_item);
        how_to_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		HelpWindow.show();
        	}
        });
        
        
        //if the lightweight menu is not disabled, then the menus will render
        //underneath the canvas when the camera's toolbar is enabled.
        file_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        edit_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        view_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        disk_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        torus_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        equatorial_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        wind_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        cone_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        help_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        
        menu_bar.add(file_menu);
        menu_bar.add(edit_menu);
        menu_bar.add(view_menu);
        menu_bar.add(disk_menu);
        menu_bar.add(torus_menu);
        menu_bar.add(equatorial_menu);
        menu_bar.add(wind_menu);
        menu_bar.add(cone_menu);
        menu_bar.add(help_menu);
    }

    /**
     * creates a JMenu to control shading and geometry of an object
     * 
     * @param object The object to manipulate
     * @return a JMenu for controlling the object
     */
    private JMenu createObjectMenu(final AGNObject object) {
    	JMenu menu = new JMenu(object.getIdentifier());
    	
        final JCheckBoxMenuItem shading_item = new JCheckBoxMenuItem("Enable Shading");
        menu.add(shading_item);
        
        shading_item.setMnemonic(KeyEvent.VK_E);
        shading_item.setSelected(true);
        
        shading_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		object.enableShading(shading_item.isSelected());
        	}
        });
        
        
    	ButtonGroup geometry_group = new ButtonGroup();
        final JRadioButtonMenuItem wireframe_item = new JRadioButtonMenuItem("Wireframe");
        final JRadioButtonMenuItem solid_item = new JRadioButtonMenuItem("Solid Shapes");
        final JMenuItem color_item = new JMenuItem("Color");
        
        geometry_group.add(solid_item);
        geometry_group.add(wireframe_item);
        
        JCheckBoxMenuItem activate = config_window.getRegionCheckbox(object.getIdentifier());
        JCheckBoxMenuItem secondary = null;
        if(object.getIdentifier().equals("Cone")) {
        	secondary = config_window.getRegionCheckbox("Counter-cone");
        }
        
        menu.add(solid_item);
        menu.add(wireframe_item);
        //we don't allow the disk to be colored, so don't present the option
        if(!object.getIdentifier().equals("Emission Disk")) {
        	menu.add(color_item);
        }
        if(activate != null) {
        	menu.add(activate);
        	if(secondary != null) {
            	menu.add(secondary);
            }
        }
        
        
        solid_item.setMnemonic(KeyEvent.VK_O);
        wireframe_item.setMnemonic(KeyEvent.VK_W);
        color_item.setMnemonic(KeyEvent.VK_C);
        
        solid_item.setSelected(true);

        solid_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		object.setShadingMode(PolygonAttributes.POLYGON_FILL);
        	}
        });
        wireframe_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		object.setShadingMode(PolygonAttributes.POLYGON_LINE);
        	}
        });
        color_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color3f diffuse = object.getDiffuseColor();
        		int r, g, b;
        		if(diffuse == null) {
        			r = 255;
        			g = 255;
        			b = 255;
        		} else {
	        		r = (int)(diffuse.getX()*255);
	        		g = (int)(diffuse.getY()*255);
	        		b = (int)(diffuse.getZ()*255);
        		}
        		int rgb = r << 16 | g << 8 | b;
        		Color initial = new Color(rgb);
        		Color color = JColorChooser.showDialog(window, "Choose color for " + object.getIdentifier(), initial);
        		if(color != null) {
        			Color3f new_color = new Color3f(color.getRed(), color.getGreen(), color.getBlue());
        			new_color.scale(1.0f/255.0f);
        			object.setColor(new_color);
        		}
        	}
        });
        
    	
    	return menu;
    }

    /**
     * creates a JMenu for changing the various HUD colors
     * 
     * @return a JMenu that allows for changing of HUD colors
     */
    private JMenu createHudMenu() {
        JMenu hud_menu = new JMenu("Select HUD Colors");
        hud_menu.setMnemonic(KeyEvent.VK_H);
        hud_menu.getPopupMenu().setLightWeightPopupEnabled(false);
        
        JMenuItem control_axis_item = new JMenuItem("Control Axis");
        JMenuItem selected_item = new JMenuItem("Selected HUD");
        JMenuItem linear_item = new JMenuItem("Linear HUD");
        JMenuItem angular_item = new JMenuItem("Angular HUD");
        JMenuItem rotational_item = new JMenuItem("Rotational HUD");

        control_axis_item.setMnemonic(KeyEvent.VK_C);
        selected_item.setMnemonic(KeyEvent.VK_S);
        linear_item.setMnemonic(KeyEvent.VK_L);
        angular_item.setMnemonic(KeyEvent.VK_A);
        rotational_item.setMnemonic(KeyEvent.VK_R);

        control_axis_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, ((JMenuItem)e.getSource()).getText() + " Color", AGNModelCanvas.getControlAxisColor());
        		if(color != null) {
        			AGNModelCanvas.setControlAxisColor(color);
        		}
        	}
        });
        selected_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, ((JMenuItem)e.getSource()).getText() + " Color", AGNModelCanvas.getSelectColor());
        		if(color != null) {
        			AGNModelCanvas.setSelectColor(color);
        		}
        	}
        });
        linear_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, ((JMenuItem)e.getSource()).getText() + " Color", AGNModelCanvas.getLinearColor());
        		if(color != null) {
        			AGNModelCanvas.setLinearColor(color);
        		}
        	}
        });
        angular_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, ((JMenuItem)e.getSource()).getText() + " Color", AGNModelCanvas.getAngularColor());
        		if(color != null) {
        			AGNModelCanvas.setAngularColor(color);
        		}
        	}
        });
        rotational_item.addActionListener(new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		Color color = JColorChooser.showDialog(window, ((JMenuItem)e.getSource()).getText() + " Color", AGNModelCanvas.getRotationalColor());
        		if(color != null) {
        			AGNModelCanvas.setRotationalColor(color);
        		}
        	}
        });

        hud_menu.add(control_axis_item);
        hud_menu.add(selected_item);
        hud_menu.add(linear_item);
        hud_menu.add(angular_item);
        hud_menu.add(rotational_item);

        return hud_menu;
    }
    
    
    /**
     * displays a dialog alert box with the given message
     * @param message The message to display
     */
	private void error(String message) {
		JOptionPane.showMessageDialog(window, message, "Error!", JOptionPane.ERROR_MESSAGE);
	}
}
